package tokenizer;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;

import tokenizer.TokenNostro.Type;

/**
 * Tokenizer
 * 
 * @author navas
 * 
 */
public class TokenizerNostro {

	private static final int EOS = -1;
	private final BufferedReader in;
	private int ch;
	private TokenNostro token;
	private int prev;
	boolean segno = false;
	private StringBuilder sb = new StringBuilder();

	// Costruttore
	public TokenizerNostro(Reader in) {
		this.in = new BufferedReader(in);
	}

	/**
	 * next
	 * 
	 * @return
	 * @throws IOException
	 */
	public TokenNostro next() throws IOException {
		skipWhites();
		if (isIdStart())
			return ident();
		if (isNum())
			return num();
		switch (ch) {
		case EOS:
			return token = new TokenNostro(Type.EOS);
		case '+':
			return PlusOp();
		case '-':
			return MinusOp();
		case '*':
			return TimesOp();
		case '/':
			return DivOp();
		case ';':
			return Semicolon();
		case '=':
			return EqualsOp();
		case '(':
			return OpenPar();
		case ')':
			return ClosePar();
		}

		reset();
		throw new RuntimeException("Token non riconosciuto");
	}

	private TokenNostro ClosePar() throws IOException {
		TokenNostro t = new TokenNostro(Type.CloseParen);
		System.out.println(t);
		next();
		return t;
	}

	private TokenNostro OpenPar() throws IOException {
		TokenNostro t = new TokenNostro(Type.OpenParen);
		System.out.println(t);
		next();
		return t;
	}

	private TokenNostro Semicolon() throws IOException {
		TokenNostro t = new TokenNostro(Type.Semicolon);
		System.out.println(t);
		next();
		return t;
	}

	private TokenNostro EqualsOp() throws IOException {
		TokenNostro t = new TokenNostro(Type.Becomes);
		System.out.println(t);
		next();
		return t;

	}

	private TokenNostro DivOp() throws IOException {
		TokenNostro t = new TokenNostro(Type.Div);
		System.out.println(t);
		next();
		return t;

	}

	private TokenNostro TimesOp() throws IOException {
		TokenNostro t = new TokenNostro(Type.Times);
		System.out.println(t);
		next();
		return t;
	}

	private TokenNostro MinusOp() throws IOException {
		TokenNostro t = new TokenNostro(Type.Minus);
		System.out.println(t);
		next();
		return t;

	}

	private TokenNostro PlusOp() throws IOException {
		TokenNostro t = new TokenNostro(Type.Plus);
		System.out.println(t);
		next();
		return t;
	}

	private TokenNostro num() throws IOException {
		resetBuilder();
		append();
		if (isDigit()) {
			integerPart();
		}
		if (isFractionIndicator()) {
			fractionIndicator();
			if (isDigit()) {
				fractionPart();
			}
		}
		if (isExpIndicator()) {
			expIndicator();

			if (isExponent()) {
				exponentPart();
			}
		}

		TokenNostro t = TokenNostro.number(sb.toString());
		System.out.println(t);
		reset();
		return t;
	}

	private boolean isExponent() {
		return Character.isDigit(ch) || ch == '+' || ch == '-';
	}

	private void expIndicator() throws IOException {
		prev = ch;
		markAndRead(1);
	}

	/**
	 * 
	 * @throws IOException
	 */
	private void exponentPart() throws IOException {
		// salvo il valore e controllo se dopo c'e' un'esponente o altro
		int expPrev = ch;
		for (;;) {
			markAndRead(1);
			if ((!isDigit()) && ch != '+' && ch != '-') {
				break;
			}
			if (ch == '+' || ch == '-' || segno == false) {
				sb.append((char) prev);
				sb.append((char) expPrev);
				segno = true;
			}
			append();
		}

	}

	private boolean isExpIndicator() {
		return ch == 'e' || ch == 'E';
	}

	/**
	 * non funziona 2..4
	 * 
	 * @throws IOException
	 */
	private void fractionPart() throws IOException {
		append();
		for (;;) {
			markAndRead(1);
			if (!isDigit())
				break;
			append();
		}
	}

	private void fractionIndicator() throws IOException {

		append();
		markAndRead(2);

	}

	private boolean isFractionIndicator() {
		return ch == '.';
	}

	/**
	 * 
	 * @throws IOException
	 */
	private void integerPart() throws IOException {
		for (;;) {
			markAndRead(1);
			if (!isDigit())
				break;
			append();
		}

	}

	private TokenNostro ident() throws IOException {
		resetBuilder();
		append();
		for (;;) {
			markAndRead(1);
			if (!isIdentPart())
				break;
			append();
		}
		TokenNostro t = TokenNostro.ident(sb.toString());
		System.out.println(t);
		reset();
		return t;
	}

	private String builderToString() {
		return sb.toString();
	}

	private void resetBuilder() {
		sb.setLength(0);

	}

	private void append() throws IOException {
		sb.append((char) ch);

	}

	public Type OpToken() throws IOException {
		read();
		while (ch != -1) {
			switch (ch) {
			case '+':
				return TokenNostro.Type.Plus;
			case '-':
				return TokenNostro.Type.Minus;
			case '*':
				return TokenNostro.Type.Times;
			case '/':
				return TokenNostro.Type.Div;
			}
		}
		return null;

	}

	/** Mangiatore di spazi bianchi */
	private void skipWhites() throws IOException {
		do {
			read();
		} while (isWhite());
	}

	private void read() throws IOException {
		ch = in.read();
	}

	private void markAndRead(int readLimit) throws IOException {
		in.mark(readLimit);
		read();
	}

	private void reset() throws IOException {
		in.reset();
		next();

	}

	private boolean isWhite() {
		return Character.isWhitespace(ch);
	}

	private boolean isIdStart() {
		return Character.isLetter(ch) || ch == '_';
	}

	private boolean isIdentPart() {
		return Character.isLetterOrDigit(ch) || ch == '_';
	}

	private boolean isDigit() {

		return Character.isDigit(ch);
	}

	private boolean isNum() {

		return Character.isDigit(ch) || ch == '.';
	}

}
